/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-13
 * V4.0
 */
package	com.jphenix.kernel.script;

import com.jphenix.kernel.classloader.BeanClassLoader;
import com.jphenix.kernel.classloader.ResourceEntry;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanFactoryManager;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.DebugUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.script.IScriptLoader;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 脚本类加载器
 * 
 * 因为热替换程序，需要构造新的类加载器。这一点，不用再去测试证实了，已经反复走弯路了。
 * 
 * 关于原理，零散的说一下，避免再次开发类加载器时再走弯路。
 * 
 * 1. 类更新后，需要热替换时，必须构建个新的类加载器，并且将当前类加载器作为父类加载器传入。
 * 2. 凡是调用defineClass方法获得的类，这个类的类加载器就是当前类加载器。
 * 3. 加载类时，先判断要加载的类，是不是在当前类加载器的缓存中。如果不在，先从父类加载器中获取
 *    （看看是不是已经被加载了），再没有，再调用defineClass方法
 * 4. 这是走了很多弯路总结出来的，听话，别再走弯路了。
 * 
 * 2019-01-16 继承了基础类加载器BeanClassLoader，这样可以支持指定脚本加载扩展jar包
 * 2019-01-24 修改了脚本支持扩展包路径后的运行错误。修改了直接调用脚本时发生的错误
 * 2019-01-24 发现脚本类加载器不能跟上一级的BeanFactory公用一个资源管理服务（ResourceService），
 *            因为原本应该用BeanFactory的类加载器加载的类，被ScriptClassLoader从BeanFactory的资
 *            源管理服务中获取到二进制资源，然后重新构造了一个类，导致两个类不是同一个类加载器构
 *            造的，造成致命的连接错误。
 * 2019-02-11 重写了findResource方法，否则脚本中无法加载私有扩展库文件中的非程序文件
 * 2019-07-08 增加了toString方法，打印出当前类加载器管理的扩展路径
 * 2019-07-20 适应ScriptVO中的绝对路径改为相对路径
 * 2019-07-20 修改了死循环错误
 * 2019-11-04 修改了，扩展类路径中引用了其它扩展类路径时无效的错误
 * 2020-05-14 增加了通过脚本信息类从缓存中移除对应的类
 * 2020-05-18 在报错时，增加了输出扩展库路径信息
 * 2020-05-24 去掉了重复输出的报错日志
 * 2020-06-11 修改了找不到指定类时抛的异常（之前抛空指针）
 * 
 * 
 * @author 刘虻
 * 2009-11-18下午02:10:53
 */
@ClassInfo({"2020-06-11 18：20","脚本类加载器"})
public class ScriptClassLoader extends BeanClassLoader {
    
	private ScriptLoader         sl              = null; //脚步加载器
    private ScriptClassLoader    parent          = null; //父类类加载器
    private List<String>         extLibPathList  = null; //扩展包路径数组
    private Map<String,Class<?>> cache           = null; //脚本类对象缓存
    
    
	/**
	 * 构造函数
	 * 2009-11-18下午02:19:05
	 */
	public ScriptClassLoader(ScriptClassLoader parent,ScriptLoader sl,String[] extLibPaths) {
		super(parent==null?
		         ((IBeanFactoryManager)sl.getBeanFactory()).getClassLoader()
			   : parent,sl.getResourceService()
		     );
		this.sl          = sl;
		if(parent!=null) {
			this.parent = parent;
			//加载扩展类路径
			extLibPathList = loadExtLibs(extLibPaths,sl.infPath("/ext_lib/"));
		}
	}
	
	/**
	 * 从缓存中移除类
	 * @param basePath 类路径
	 * 2019年1月21日
	 * @author MBG
	 */
	public void remove(String basePath,String fileName) {
		//文件路径序列，包含子类路径
		List<String> fileList = new ArrayList<String>();
		try {
			//搜索这个文件夹，找到这个类文件和这个类的子类文件
			SFilesUtil.getFileList(fileList,basePath,fileName,"class",false,true);
		}catch(Exception e) {
			e.printStackTrace();
			return;
		}
		for(String path:fileList) {
			getCache().remove(IScriptLoader.SCRIPT_PACKAGE+"."+SFilesUtil.getFileBeforeName(path));
		}
	}
	
	/**
	 * 从缓存中移除指定脚本类信息
	 * @param sVO 脚本信息类
	 * 2020年5月14日
	 * @author MBG
	 */
	public void remove(ScriptVO sVO) {
		if(sVO!=null) {
			getCache().remove(sVO.classPath);
		}
	}

	/**
	 * 加载指定脚本类
	 * @param sVO        脚本信息类
	 * @return           脚本类对象
	 * @throws Exception 异常
	 * 2016年4月11日
	 * @author MBG
	 */
	public Class<?> loadClass(ScriptVO sVO) throws Exception {
		//从缓存中获取
		Class<?> reCls = getCache().get(sVO.classPath);
		if(reCls!=null) {
			return reCls;
		}
        //读取文件为字节数组
        byte[] classBytes = getScriptLoader().fm.getClassBytes(getClassLastName(sVO.classPath));
        if(classBytes==null) {
        	throw new ClassNotFoundException(sVO.classPath);
        }
    	try {
    		reCls = defineClass(sVO.classPath, classBytes, 0, classBytes.length);
    		
    		getCache().put(sVO.classPath,reCls);
    	}catch(NoClassDefFoundError e) {
    		e.printStackTrace();
    		throw new ClassNotFoundException(e.getMessage());
    	}
    	return reCls;
	}
	
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-8-30 下午09:52:24
	 */
    @Override
    public URL findResource(final String name) {
		//整理路径
		String               fixName = BaseUtil.swapString(name,"\\","/");
		ResourceEntry re           = rs.getSource(fixName,extLibPathList); //从全局包资源管理服务中获取资源对象
		if(re==null) {
			//不要在这里打日志，因为很多时候是尝试性寻找，不一定非得找到
			//System.err.println("findResource Not Find The Name:["+name+"]  fixName:["+fixName+"]");
			return null;
		}
		try {
			return re.toUrl();
		}catch(Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
    /**
     * 覆盖方法（不能删除）
     * 加载指定类
     * @param className     类名
     * @throws Exception    异常
     * @return              类对象
     * 2014年6月21日
     * @author 马宝刚
     */
	@Override
    public Class<?> loadClass(String className) throws ClassNotFoundException {
		if(className==null || className.length()<1) {
			throw new ClassNotFoundException("The ClassPath is Empty :["+className+"]");
		}
		if(className.startsWith(IScriptLoader.SCRIPT_PACKAGE)) {
			//当前脚本类的内部子类，比如脚本内部的线程类
			//从缓存中获取
			Class<?> reCls = getCache().get(className);
			if(reCls!=null) {
				return reCls;
			}
	        //读取文件为字节数组
	        byte[] classBytes = getScriptLoader().fm.getClassBytes(getClassLastName(className));
	    	if(classBytes==null) {
	    		throw new ClassNotFoundException(className);
	    	}
	        try {
	    		reCls = defineClass(className, classBytes, 0, classBytes.length);
	    		getCache().put(className,reCls);
	    	}catch(Exception e) {
	    		e.printStackTrace();
	    		throw new ClassNotFoundException(e.toString());
	    	}
	    	return reCls;
		}
		try {
			return findClass(className);
		}catch(Exception e) {}
		// 如果该类加载器是一个子类加载器，只负责新构建的类。如果通过这个类加载器加载父类加载器管理的类
		// 在第一步findClass方法，就会抛异常，执行到此步
		// 如果在此步用findSystemClass获取到的类是一个新的类，而并不是正在使用的类，从而导致类型不匹配出错
//				try {
//					//先判断是否为系统类
//					return super.findSystemClass(name);
//				}catch(Exception e) {}
				try {
					//先判断是否为系统类
					return super.loadClass(className);
				}catch(Exception e) {}
				try {
					//先判断是否为系统类
					return super.findSystemClass(className);
				}catch(Exception e) {}
				try {
					//在从本地加载器中获取
					return super.loadClass(className);
				}catch(Exception e) {}
				//获取类路径
				URL[] urls = getURLs();
				//构建报错信息
				StringBuffer sbf = new StringBuffer();
				sbf
					.append("\n\nNot Find The ClassPath:["+className+"] ClassLoader:"+this)
					.append("\n==============================URL List ================================== Count:[\"+urls.length+\"]\n");
				if(urls!=null) {
					for(int i=0;i<urls.length;i++) {
						sbf.append(urls[i]).append("\n");
					}
				}
				sbf.append("\n==============================URL List===================================\n");
				
				if(extLibPathList!=null) {
					sbf.append("\n==============================ExtLibs================================== Count:["+extLibPathList.size()+"]\n");
					for(String path:extLibPathList) {
						sbf.append(path).append("\n");
					}
					sbf.append("\n==============================ExtLibs===================================\n");
				}else {
					sbf.append("\n############################ No ExtLibs ############################\n");
				}
				sbf.append(DebugUtil.getStackTraceString(this));
				throw new ClassNotFoundException("\nNot Find The ClassPath:["+className+"] "+sbf);
	}
	

	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-8-30 下午09:51:47
	 */
    @Override
    public URL[] getURLs() {
    	return rs.getURLs(extLibPathList);
    }
    
    
    /**
     * 覆盖方法
     */
    @Override
    public String toString() {
    	//构建返回值
    	StringBuffer reSbf = new StringBuffer();
    	reSbf
    		.append("ScriptClassLoader:\n")
    		.append("\n==============================ExtLibPath List ================================== Count:[\"")
    		.append(extLibPathList==null?"0":extLibPathList.size())
    		.append("\"]\n");
    	if(extLibPathList!=null) {
    		for(String path:extLibPathList) {
    			reSbf.append(path).append("\n");
    		}
    	}
    	reSbf.append("\n==============================ExtLibPath List===================================\n");
    	
    	URL[] urls = getURLs(); //获取加载了的URL
    	reSbf
    		.append("\n==============================URL List ================================== Count:[\"")
    		.append(urls==null?"0":urls.length)
    		.append("\"]\n");
    	if(urls!=null) {
    		for(int i=0;i<urls.length;i++) {
    			reSbf.append(urls[i]).append("\n");
    		}
    	}
    	reSbf.append("\n==============================URL List===================================\n");
    	return reSbf.toString();
    }
	
	//////////////以下是内部类方法//////////////////////////////////////////////
	
    
    /**
     * 覆盖方法（不能删除）
     */
    @Override
    protected Class<?> findClass(final String name) throws ClassNotFoundException {
    	//通过类路径获取对应的相对路径
    	String classKey  = BaseUtil.swapString(name,".","/")+".class";
    	ResourceEntry re = rs.getSource(classKey,extLibPathList); //资源对象
    	if(re==null) {
    		throw new ClassNotFoundException(name);
    	}
    	if(re.loadedClass==null) {
    		createClass(name,re);
    	}else {
	    	//检测是否发生变化
	    	File cTimeFile;
	    	if(re.inJar) {
	    		cTimeFile = new File(re.codeBase);
	    	}else{
	    		cTimeFile = new File(re.source);
	    	}
	    	if(re.lastModified!=cTimeFile.lastModified()) {
	    		//文件发生了变化
	    		createClass(name,re);
	    	}
    	}
    	return re.loadedClass;
    }
    
    /**
     * 获取缓存对象
     * @return 缓存对象
     * 2019年1月21日
     * @author MBG
     */
    protected Map<String,Class<?>> getCache(){
    	if(parent==null || parent.equals(this)) {
    		if(cache==null) {
    			cache = new HashMap<String,Class<?>>();
    		}
    		return cache;
    	}
    	return parent.getCache();
    }
	
	
	/**
	 * 获取脚本类加载器
	 * @return 脚本类加载器
	 * 2019年1月18日
	 * @author MBG
	 */
	protected ScriptLoader getScriptLoader() {
		if(parent==null || parent.equals(this)) {
			return sl;
		}
		return parent.getScriptLoader();
	}
	
	/**
	 * 通过完整类名获取末尾类名（去掉com.jphenix.script包路径）
	 * @param classPath 完整类名
	 * @return 末尾类名
	 * 2016年4月12日
	 * @author MBG
	 */
	private String getClassLastName(String classPath) {
		//获取分隔符
		int point = classPath.lastIndexOf(".");
		if(point>0) {
			return classPath.substring(point+1);
		}
		return classPath;
	}
}
